using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;

namespace SCDL {
    // class generating XSD from ScdlDocument
    class ConfigSchemaGenerator {
        XmlDocument _xmlDoc;

        public ConfigSchemaGenerator() {
        }

        public void GenerateSchema(ScdlDocument doc, TextWriter outputSchema) {
            XmlDocument xmlDoc = GenerateSchemaXml(doc);
            xmlDoc.Save(outputSchema);
        }

        public XmlDocument GenerateSchemaXml(ScdlDocument doc) {
            try {
                _xmlDoc = new XmlDocument();
                _xmlDoc.AppendChild(CreateXmlNode(doc));
                return _xmlDoc;
            }
            finally {
                _xmlDoc = null;
            }
        }

        // xml node for the entire document
        XmlElement CreateXmlNode(ScdlDocument doc) {
            XmlElement root = CreateXmlNode("schema");
            if (!string.IsNullOrEmpty(doc.TargetNamespace))
            {
                root.SetAttribute("targetNamespace", doc.TargetNamespace);
            }
            root.SetAttribute("elementFormDefault", "qualified");

            /*XmlElement rootSectionNode = CreateXmlNode("element", "name", doc.Namespace);
            root.AppendChild(rootSectionNode);
            XmlElement rootSectionType = CreateXmlNode("complexType");
            rootSectionNode.AppendChild(rootSectionType);
            XmlElement rootSequence = CreateXmlNode("sequence");
            rootSectionType.AppendChild(rootSequence);*/

            // add sections
            foreach (ScdlSection section in doc.Sections) {
                root.AppendChild(CreateXmlNode(section));
            }

            return root;
        }

        // xml node for a section
        XmlElement CreateXmlNode(ScdlSection section) {
            XmlElement sectionNode = CreateXmlNode("element", "name", section.Name);
            XmlElement sectionType = CreateXmlNode("complexType");

            // add subcollections and subnodes if any
            if (section.SubCollections.Count + section.SubNodes.Count > 0) {
                XmlElement choiceNode = CreateXmlNode("choice", "minOccurs", "0", "maxOccurs", "unbounded");

                foreach (ScdlNode element in section.SubNodes) {
                    choiceNode.AppendChild(CreateXmlNode(element));
                }

                foreach (ScdlCollection collection in section.SubCollections) {
                    choiceNode.AppendChild(CreateXmlNode(collection));
                }

                sectionType.AppendChild(choiceNode);
            }

            // add attributes
            foreach (ScdlAttribute attr in section.Attributes) {
                if (attr.Name != "xmlns")
                    sectionType.AppendChild(CreateXmlNode(attr));
            }

            sectionNode.AppendChild(sectionType);
            return sectionNode;
        }

        // xml node for an element
        XmlElement CreateXmlNode(ScdlNode element) {
            XmlElement elemNode = CreateXmlNode("element", "name", element.Name);
            XmlElement elemType = CreateXmlNode("complexType");

            // add attributes
            foreach (ScdlAttribute attr in element.Attributes) {
                if (attr.Name != "xmlns")
                    elemType.AppendChild(CreateXmlNode(attr));
            }

            elemNode.AppendChild(elemType);
            return elemNode;
        }

        // xml node for a collection
        XmlElement CreateXmlNode(ScdlCollection collection) {
            XmlElement collNode = CreateXmlNode("element", "name", collection.Name);
            XmlElement collType = CreateXmlNode("complexType");

            // add/remove/clear constructs:
            //
            //  <xs:choice minOccurs="0" maxOccurs="unbounded">
            //      <xs:element name="add">
            //          <xs:complexType>
            //              <xs:attribute .../>
            //              ...
            //          </xs:complexType>
            //      </xs:element>
            //      <xs:element name="remove">
            //          <xs:complexType>
            //              <xs:attribute .../>
            //          </xs:complexType>
            //      </xs:element>
            //      <xs:element name="clear">
            //          <xs:complexType/>
            //      </xs:element>
            //  </xs:choice>

            XmlElement choiceNode = CreateXmlNode("choice", "minOccurs", "0", "maxOccurs", "unbounded");

            XmlElement addNode = CreateXmlNode("element", "name", "add");
            XmlElement removeNode = CreateXmlNode("element", "name", "remove");
            XmlElement clearNode = CreateXmlNode("element", "name", "clear");

            XmlElement addType = CreateXmlNode("complexType");
            XmlElement removeType = CreateXmlNode("complexType");
            XmlElement clearType = CreateXmlNode("complexType");

            //Allow "add" to contain any sort of XML
            XmlAttribute attrAdd = _xmlDoc.CreateAttribute("minOccurs");
            attrAdd.Value = "0";
            XmlElement addSequence = CreateXmlNode("sequence");
            addSequence.Attributes.Append(attrAdd);
            XmlElement addAnyContent = CreateXmlNode("any");
            attrAdd = _xmlDoc.CreateAttribute("processContents");
            attrAdd.Value = "skip";
            addAnyContent.Attributes.Append(attrAdd);
            addType.AppendChild(addSequence);
            addSequence.AppendChild(addAnyContent);

            


            foreach (ScdlAttribute attr in collection.ItemAttributes) {
                if (attr.Name != "xmlns")
                    addType.AppendChild(CreateXmlNode(attr));

                if (attr.IsKey) {
                    removeType.AppendChild(CreateXmlNode(attr));
                }
            }

            addNode.AppendChild(addType);
            removeNode.AppendChild(removeType);
            clearNode.AppendChild(clearType);

            choiceNode.AppendChild(addNode);
            choiceNode.AppendChild(removeNode);
            choiceNode.AppendChild(clearNode);

            collType.AppendChild(choiceNode);

            // attributes
            foreach (ScdlAttribute attr in collection.Attributes) {
                collType.AppendChild(CreateXmlNode(attr));
            }

            collNode.AppendChild(collType);
            return collNode;
        }

        // xml node for an attribute
        XmlElement CreateXmlNode(ScdlAttribute attr) {
            XmlElement attrNode = CreateXmlNode("attribute", "name", attr.Name);
            AddXmlAttributeToNode(attrNode, "use",  attr.IsRequired ? "required" : "optional");

            if (attr.ValueType == typeof(string)) {
                AddXmlAttributeToNode(attrNode, "type", "xs:string");
            }
            else if (attr.ValueType == typeof(bool)) {
                //  <xs:simpleType>
                //      <xs:restriction base="xs:NMTOKEN">
                //          <xs:enumeration value="false" />
                //          <xs:enumeration value="true" />
                //      </xs:restriction>
                //  </xs:simpleType>

                XmlElement st = CreateXmlNode("simpleType");
                XmlElement rs = CreateXmlNode("restriction", "base", "xs:NMTOKEN");
                rs.AppendChild(CreateXmlNode("enumeration", "value", "false"));
                rs.AppendChild(CreateXmlNode("enumeration", "value", "true"));
                st.AppendChild(rs);
                attrNode.AppendChild(st);
            }
            else if (attr.ValueType == typeof(TimeSpan)) {
                //  <xs:simpleType>
                //      <xs:restriction base="xs:string">
                //          <xs:pattern value="([0-9.]+:){0,1}([0-9]+:){0,1}[0-9.]+" />
                //      </xs:restriction>
                //  </xs:simpleType>

                XmlElement st = CreateXmlNode("simpleType");
                XmlElement rs = CreateXmlNode("restriction", "base", "xs:string");
                rs.AppendChild(CreateXmlNode("pattern", "value", "([0-9.]+:){0,1}([0-9]+:){0,1}[0-9.]+"));
                st.AppendChild(rs);
                attrNode.AppendChild(st);
            }
            else if (attr.ValueType == typeof(int)) {
                AddXmlAttributeToNode(attrNode, "type", "xs:int");
            }
            else {
                throw new InvalidOperationException(
                    string.Format("Unexpected attribute type '{0}'", attr.ValueType.Name));
            }

            if (!attr.IsRequired && !string.IsNullOrEmpty(attr.Value)) {
                AddXmlAttributeToNode(attrNode, "default", attr.Value);
            }

            return attrNode;
        }

        // simple xml node
        XmlElement CreateXmlNode(string name) {
            return _xmlDoc.CreateElement("xs", name, "http://www.w3.org/2001/XMLSchema");
        }

        // simple xml node with 1 attribute
        XmlElement CreateXmlNode(string name, string attrName, string attrValue) {
            XmlElement node = CreateXmlNode(name);
            AddXmlAttributeToNode(node, attrName, attrValue);
            return node;
        }

        // simple xml node with 2 attributes
        XmlElement CreateXmlNode(string name, string attr1Name, string attr1Value,
                                        string attr2Name, string attr2Value) {
            XmlElement node = CreateXmlNode(name);
            AddXmlAttributeToNode(node, attr1Name, attr1Value);
            AddXmlAttributeToNode(node, attr2Name, attr2Value);
            return node;
        }

        // helper to add an attribute to xml node
        void AddXmlAttributeToNode(XmlNode node, string name, string value) {
            XmlAttribute attr = _xmlDoc.CreateAttribute(name);
            attr.Value = value;
            node.Attributes.Append(attr);
        }
    }
}
